package core

import (
	"context"
	"freechat-im/common"
	"freechat-im/config"
	"freechat-im/external"
	"freechat-im/model"
	"freechat-im/support"
	"gitee.com/goddesszz/gocommon/myutil"
	"github.com/sirupsen/logrus"
	"strconv"
	"time"
)

type MessageHandler struct {
	onlineRegister OnlineUserRegister
}

func NewMessageHandle(onlineRegister OnlineUserRegister) *MessageHandler {
	return &MessageHandler{
		onlineRegister: onlineRegister,
	}
}

// 发送message到client端后的回调
func (handler *MessageHandler) AsyncCallback(meta map[string]string, isSuccess bool) {
	msgId, _ := strconv.ParseInt(meta["msgId"], 10, 64)
	receiver := meta["receiver"]
	go func() {
		support.GetMQProducer().ToChangeMsgStatus(model.MsgStatusChange{
			MsgId:     msgId,
			MsgStatus: model.Status_SENT,
			Extra: map[string]string{
				"isSuccess": strconv.FormatBool(isSuccess),
				"receiver":  receiver,
			},
		})
	}()
}

// 处理接收到的message
func (handler *MessageHandler) Handle(msgWrap *model.MessageWrap) {
	var userId = msgWrap.UserId
	if userId == "" {
		logrus.Error("当前的userId不能为空")
	}
	if msgWrap.Event != nil {
		handler.handleEventMsg(msgWrap.Event, msgWrap.UserId)
	}
	if msgWrap.Message != nil {
		handler.handleMessage(msgWrap.Message, msgWrap.UserId)
	}
}

func (handler *MessageHandler) handleEventMsg(event *model.MsgEvent, userId string) {
	//处理客户端发起的事件
	switch event.Event {
	case model.TYPING: //输入中 ->  THE_OTHER_TYPING
		if event.ChatId == "" {
			logrus.Error("event消息的 ChatId 必填")
		}
		//事件来自A，发送给B
		var typingEvent = &model.MsgEvent{
			Action:  model.THE_OTHER_TYPING,
			ChatId:  event.ChatId,
			IsGroup: event.IsGroup,
			SendTo:  event.SendTo,
		}
		handler.TransferEventMsg(typingEvent, userId)
	case model.READ: //消息已读 ->  THE_OTHER_READ
		go func() {
			support.GetMQProducer().ToChangeMsgStatus(model.MsgStatusChange{
				MsgId:     event.MsgId,
				MsgStatus: model.Status_READ,
				Extra:     map[string]string{"userId": userId},
			})
		}()
		//事件来自A，发送给B
		//如果B是group
		var readEvent = &model.MsgEvent{
			Action:  model.THE_OTHER_READ,
			MsgId:   event.MsgId,
			ChatId:  event.ChatId,
			IsGroup: event.IsGroup,
			SendTo:  event.SendTo,
		}

		handler.TransferEventMsg(readEvent, userId)
	case model.RECALL:
		//事件来自A，发送给A、B
		if isAllowReCall(event) {
			if event.ChatId == "" {
				logrus.Error("event消息的 ChatId 必填")
			}
			go func() {
				support.GetMQProducer().ToChangeMsgStatus(model.MsgStatusChange{
					MsgId:     event.MsgId,
					MsgStatus: model.Status_RECALL,
				})
			}()
			//发送给A
			var allowReCallEvent = &model.MsgEvent{
				Action:  model.RECALL_ALLOW,
				MsgId:   event.MsgId,
				LocalId: event.LocalId,
				ChatId:  event.ChatId,
				IsGroup: event.IsGroup,

				SendTo: userId,
			}
			handler.TransferEventMsg(allowReCallEvent, userId)
			//发送给B
			//如果B是group
			var reCalledEvent = &model.MsgEvent{
				Action:  model.THE_RECALLED,
				MsgId:   event.MsgId,
				LocalId: event.LocalId,
				ChatId:  event.ChatId,
				IsGroup: event.IsGroup,

				SendTo: event.SendTo,
			}
			handler.TransferEventMsg(reCalledEvent, userId)
		} else {
			var forbidReCallEvent = &model.MsgEvent{
				Action:  model.RECALL_FORBID,
				MsgId:   event.MsgId,
				LocalId: event.LocalId,
				ChatId:  event.ChatId,
				IsGroup: event.IsGroup,

				SendTo: userId,
			}
			handler.TransferEventMsg(forbidReCallEvent, userId)
		}
	}
}

func (handler *MessageHandler) handleMessage(message *model.ChatMessage, userId string) {
	//填充message的其他属性值
	message.Sender = userId
	message.Id = common.AutoIdNode.Generate().Int64() //为message生成全局唯一ID
	message.CreateTime = time.Now()
	if message.IsGroup {
		message.ChatId = message.Receiver //Receiver是groupId
	} else {
		var chatRelId = external.GetChatRelation(message.Sender, message.Receiver)
		message.ChatId = chatRelId
	}

	//给client端发送一个应答事件
	var msgEvent = &model.MsgEvent{
		Action:  model.ACK,
		ChatId:  message.ChatId,
		MsgId:   message.Id,
		LocalId: message.LocalId,
		IsGroup: message.IsGroup,
		SendTo:  userId,
	}

	handler.TransferEventMsg(msgEvent, userId)

	//?需要创建一个conversation吗

	//转发消息
	handler.agentTransfer(message)
}

// agentTransfer 转发消息
func (handler *MessageHandler) agentTransfer(message *model.ChatMessage) {
	context.Background()
	go func() {
		config.GetRedisClient().Set(config.GetRedisContext(), common.REDIS_KEY_Msg_AllowReCall_Prefix+strconv.FormatInt(message.Id, 10), nil, common.ReCallExpiredSeconds*time.Second)
		support.GetMQProducer().ToPersist(*message)
	}()

	//2、针对不同的消息发送范围
	if message.IsGroup {
		var members = external.GetGroupMembers(message.ChatId)
		for _, member := range members {
			if member != message.Sender { //members中包含发送者自己，所以要忽略自己
				copyMsg := *message         //copy
				copyMsg.Receiver = member   //将receiver改为实际的接收者，因为群消息的receiver值是groupId
				handler.doTransfer(copyMsg) //这里不能发送message引用
			}
		}
	} else {
		handler.doTransfer(*message)
	}
}

func (handler *MessageHandler) TransferEventMsg(event *model.MsgEvent, user string) {

	if event.IsGroup {
		var members = external.GetGroupMembers(event.ChatId)
		for _, member := range members {
			copyEvent := *event //copy
			copyEvent.SendTo = member

			if member != user { //排除自己
				//走转发message的逻辑
				handler.doTransferByEventMsg(copyEvent)
			}
		}
	}

	if event.SendTo == user {
		GetService().SendToClient(*event, user)
	} else {
		//走转发message的逻辑
		handler.doTransferByEventMsg(*event)
	}

}

/*
转发消息
实现细节：将消息发送给 消息的实际接收者，可以自己给自己发消息.
两种场景
  - 单机模式时：直接发到client端
  - 集群模式时：通过Nats发布消息，订阅者去消费消息。
    根据realReceiver在哪个节点上，就往哪个节点发布消息。
    ? 集群模式时，假设获取到了userNode=A，此时user刚好离线，则A节点发消息时，A节点会认为user已经离线了，消息存入离线表
*/
func (handler *MessageHandler) doTransfer(message model.ChatMessage) {
	if message.Receiver == "" {
		logrus.Warn("message realReceiver 为空 ")
		return
	}
	var nodeName = handler.onlineRegister.GetUserNode(message.Receiver)

	// nodeName == "" 表示已离线
	if nodeName == "" || nodeName == common.NodeName {
		SendMessage(&message) //本节点直接发
	} else {
		logrus.WithFields(logrus.Fields{
			"node":  nodeName,
			"msgId": message.Id,
		}).Info(">>> transfer message to node... ")

		//通过Nats转发消息
		err := config.GetNatsConn().Publish("chat.message."+nodeName, myutil.Serialize(message))
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"node":  nodeName,
				"msgId": message.Id,
			}).Error("transfer message to node 失败。 ：err = ", err)
			//Nats挂了，补救措施：本节点直接发
			SendMessage(&message)
		}
	}
}

func (handler *MessageHandler) doTransferByEventMsg(eventMsg model.MsgEvent) {
	if eventMsg.SendTo == "" {
		logrus.Warn("message SendTo 为空 ")
		return
	}
	var nodeName = handler.onlineRegister.GetUserNode(eventMsg.SendTo)

	// nodeName == "" 表示已离线
	if nodeName == "" || nodeName == common.NodeName {
		logrus.Debug(">>> send eventMsg to client ... eventMsg=", eventMsg)
		GetService().SendToClient(eventMsg, eventMsg.SendTo) //本节点直接发，忽略失败
	} else {
		logrus.WithFields(logrus.Fields{
			"node":   nodeName,
			"action": eventMsg.Action,
			"chatId": eventMsg.ChatId,
			"msgId":  eventMsg.MsgId,
		}).Info(">>> transfer eventMsg to node... ")

		//通过Nats转发消息
		err := config.GetNatsConn().Publish("chat.eventmsg."+nodeName, myutil.Serialize(eventMsg))
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"node":   nodeName,
				"action": eventMsg.Action,
				"chatId": eventMsg.ChatId,
				"msgId":  eventMsg.MsgId,
			}).Error("transfer eventMsg to node 失败。 ：err = ", err)
			//Nats挂了，补救措施：本节点直接发
			GetService().SendToClient(eventMsg, eventMsg.SendTo) //本节点直接发
		}
	}
}

// SendMessage - 发送消息
// Send 两种情景：
// 1、如果user不在线，则存储到离线消息表中
// 2、如果user在线，则发client，同时发送异步保存消息到消息表中
func SendMessage(message *model.ChatMessage) {
	var isOnline = func() bool { return GetService().onlineRegister.IsOnline(message.Receiver) }
	if message.IsNrt || isOnline() {
		logrus.Info(">>> send message to client ... msgId=", message.Id)
		GetService().SendToClient(*message, message.Receiver)
	} else {
		message.IsNrt = true //标记为非实时消息（即离线消息）
		logrus.Info(">>> send message to offline-message-table ... msgId=", message.Id)
		go func() {
			support.GetMQProducer().ToOfflineTable(message)
		}()
	}
}

func isAllowReCall(msgEvent *model.MsgEvent) bool {
	//获取消息的发送时间，需要查 redis缓存
	var msgId = msgEvent.MsgId
	if msgId == 0 {
		return true
	}
	//去查Redis
	value, _ := config.GetRedisClient().Exists(config.GetRedisContext(), common.REDIS_KEY_Msg_AllowReCall_Prefix+strconv.FormatInt(msgId, 10)).Result()
	return value > 0 //>0表示有
}
